<?php
	class GeoBehavior extends ModelBehavior
	{
		protected $_defaults = array("latitude"=>'lat',
									'longitude'=>"lng",
									'address'=>array("address"),
									'getCordinatesOnSave'=>true,
									'city'=>null,
									'state'=>null,
									'country'=>null,
									'postal_code'=>null,
									//cache complete location to a 
									'locationCacheField'=>false
		);
		protected $Geocode;
		protected $configs = array();
		//in miles
		
		function setup(AppModel $Model,$config)
		{
			$config = am($this->_defaults,$config);
			$this->configs[$Model->alias] = $config;
		}
		
		function beforeSave($Model)
		{
			if($this->configs[$Model->alias]['getCordinatesOnSave'] == true)
			{
				$location = $Model->makeLocationArray($Model->data);
				if(count($location) > 0)
				{
					if((empty($Model->data[$Model->alias][$this->configs[$Model->alias]['longitude']]) || empty($Model->data[$Model->alias][$this->configs[$Model->alias]['latitude']])))
					{
						$latLng = $this->getGeoModel()->getCordinates($location);
						$Model->data[$Model->alias][$this->configs[$Model->alias]['latitude']] = @$latLng['lat'];
						$Model->data[$Model->alias][$this->configs[$Model->alias]['longitude']] = @$latLng['lng'];
					}
					if($this->configs[$Model->alias]['locationCacheField'] && $Model->hasField($this->configs[$Model->alias]['locationCacheField']))
					{
						$Model->data[$Model->alias][$this->configs[$Model->alias]['locationCacheField']] = implode(" ",$location);
					}
				}
			}
			return true;
		}
		
		function getGeoModel()
		{
			if(!$this->Geocode)
			{
				$this->Geocode = cri("Geo.Geocode");
			}
			return $this->Geocode;
		}
		
		function makeLocationArray($Model,$data)
		{
			$location = array();
			foreach($this->configs[$Model->alias]['address'] as $addressField)
			{
				if(isset($data[$Model->alias][$addressField]))
				{
					if(!isset($location['address']))
					{
						$location['address'] = "";
					}
						
					$location['address'] .= $Model->showValue($addressField,$data[$Model->alias])." ";
				}
				if(isset($location['address']))
				{
					$location['address'] = trim($location['address']);
				}
			}
			$locationFields = array('city','state','country','postal_code');
			foreach($locationFields as $locationField)
			{
				if($this->configs[$Model->alias][$locationField] == null || $this->configs[$Model->alias][$locationField] == false)
				{
					continue;
				}
				$field = $this->configs[$Model->alias][$locationField];
				if(isset($data[$Model->alias][$field]))
				{
						
					$location[$locationField] = $Model->showValue($field,$data[$Model->alias]);
				}
			}
			return $location;
		}
		function beforeFind(AppModel $Model,$query)
		{
			//distance should be in km
			//location should be array
			// or pass latLng array
			if(isset($query['distance']))
			{
				$latLng = null;
				if(isset($query['latLng']))
				{
					$latLng = $query['latLng'];
				}
				else if(isset($query['location']))
				{
					if(is_string($query['location']))
					{
						$query['location'] = array("address"=>$query['location']);
					}
					$latLng = $this->getGeoModel()->getCordinates($query['location']);
				}
				if($latLng)
				{
					if(!isset($query['condition']))
					{
						$query['condition'] = array();
					}
					$condition = $this->getDistanceQuery($Model,$query['distance'],$latLng['lat'],$latLng['lng']);
					$query['conditions'] = am($query['conditions'],$condition);
					$order = $query['order'][0];
					$sql = $this->getDistanceFormulaSql($Model,$latLng['lat'],$latLng['lng']);
					$dir = "ASC";
					$appendOrder  = false;
					if((isset($order['distance']) || isset($order[$Model->alias.".distance"])))
					{
						
						if(isset($order['distance']))
						{
							$dir = $order['distance'];
						}else
						{
							$dir = $order[$Model->alias.".distance"];
						}
						$appendOrder = true;
						
					}else if(isset($query['distanceOrder']))
					{
						$appendOrder = true;
						$dir = $query['distanceOrder'];
					}
					if($appendOrder)
					{
						$query['order'][0] = $sql.' '.$dir;
					}
				}
			}
			return $query;
		}
		function getDistanceQuery(AppModel $Model,$distance,$lat,$lng)
		{
			 // This portion of the routine  calculates the minimum and maximum lat and
      		// long within a given range.  This portion of the code was written
     		// by Jeff Bearer (http://www.jeffbearer.com). This significanly decreases
      		// Find Max - Min Lat / Long for Radius and zero point and query
      		// only zips in that range.
      		$latField = $this->configs[$Model->alias]['latitude'];
      		$lngField = $this->configs[$Model->alias]['longitude'];
			$lat_range = $distance/69.172;
			$lon_range = abs($distance/(cos($lat) * 69.172));
			$min_lat = number_format($lat - $lat_range, "4", ".", "");
			$max_lat = number_format($lat + $lat_range, "4", ".", "");
			$min_lon = number_format($lng - $lon_range, "4", ".", "");
			$max_lon = number_format($lng + $lon_range, "4", ".", "");
			$condition = array(
			$Model->alias.'.'.$latField.' BETWEEN ? AND ?'=>array($min_lat,$max_lat),
			$Model->alias.'.'.$lngField.' BETWEEN ? AND ?'=>array($min_lon,$max_lon));
			return $condition;
		}
		
		function getDistanceFormulaSql(AppModel $Model,$lat,$lng)
		{
			$latField = $this->configs[$Model->alias]['latitude'];
			$lngField = $this->configs[$Model->alias]['longitude'];
			$lat = deg2rad($lat);
			$lng = deg2rad($lng);
			$part1 = "POW(ASIN(([[lat]] - RADIANS([[latField]]))/2.0),2)";
			$part2A = "ACOS(RADIANS([[latField]])) * ACOS([[lat]])";
			$part2B = "POW(ASIN(([[lng]] - RADIANS([[lngField]]))/2.0),2)";
			$partCombine = "$part1 + ($part2A * $part2B)";
			$dis = GeoUtility::EARTH_RADIUS." * 2 *(ATAN2(SQRT($partCombine ),SQRT(1-$partCombine )))";
			$dis = "ROUND($dis,2)";
			
			$sql = Utility::replaceTemplateVars($dis,compact("lat",'lng','latField','lngField'));
			return $sql;
		}
	}
?>